package org.geektimes.configuration.micro.config.source;

import org.eclipse.microprofile.config.spi.ConfigSource;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Stream;

public class ConfigSources implements Iterable<ConfigSource> {
    private boolean addDefaultConfigSources;
    private boolean addDiscoveredConfigSources;
    private ClassLoader classLoader;
    List<ConfigSource> configSources = new LinkedList<>();

    public ConfigSources(ClassLoader classLoader) {
            this.classLoader = classLoader;
    }

    public void addDefaultSources() {
        if (addDefaultConfigSources){
            return;
        }
        this.addConfigSources(DefaultConfigSource.class,
                JavaPropertyConfigSource.class,
                OsPropertyConfigSource.class);
        addDefaultConfigSources = true;
    }

    public void addDiscoveredSources() {
        if (addDiscoveredConfigSources){
            return;
        }
        addConfigSources(ServiceLoader.load(ConfigSource.class, classLoader));
        addDiscoveredConfigSources = true;
    }

    @Override
    public Iterator<ConfigSource> iterator() {
        return configSources.iterator();
    }

    public void setClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    private ConfigSource newInstance(Class<? extends ConfigSource> configSourceClass) {
        ConfigSource instance = null;
        try {
            instance = configSourceClass.newInstance();
            if (MapBasedConfigSource.class.isAssignableFrom(configSourceClass)) {
                Method method = configSourceClass.getMethod("init");
                method.invoke(instance);
            }
        } catch (InstantiationException | IllegalAccessException |
                InvocationTargetException | NoSuchMethodException e) {
            throw new IllegalStateException(e);
        }
        return instance;
    }

    public void addConfigSources(Class<? extends ConfigSource>... configSourceClasses) {
        ConfigSource[] configSources = Stream.of(configSourceClasses)
                .map(this::newInstance)
                .toArray(ConfigSource[]::new);
        addConfigSources(configSources);
    }

    public void addConfigSources(ConfigSource... configSources) {
        addConfigSources(Arrays.asList(configSources));
    }

    public void addConfigSources(Iterable<ConfigSource> configSources) {
        configSources.forEach(this.configSources::add);
        Collections.sort(this.configSources, ConfigSourceComparator.INSTANCE);
    }
}
